package websocket

import (
	"encoding/json"
	"github.com/kataras/iris"
	"github.com/kataras/iris/websocket"
	"github.com/ngaut/log"
	"server/bitkop/app/constants"
	"server/bitkop/app/model"
	"server/bitkop/core"
	"sync"
	"time"
)

type websocketParams struct {
	MessageId 	constants.MsgId 	`json:"msg_id"`
	Text 		string 				`json:"text"`
	Pair 		string 				`json:"pair"`
}

const (
	DEFAULT_FLOAT_NUMBER = 4
	DEFAULT_FLOAT_PRICE = 8
)

type SocketManager struct {
	ws 			*websocket.Server
	clients   	map[websocket.Connection]string
	coinPairs 	map[string]bool
	floatPrice 	map[string]int
	floatNumber map[string]int
	sync.Mutex
}

func NewSocketManager(ws *websocket.Server) *SocketManager {
	return &SocketManager{
		ws:      ws,
		clients: make(map[websocket.Connection]string),
		coinPairs: make(map[string]bool, 0),
		floatPrice: make(map[string]int),
		floatNumber: make(map[string]int),
	}
}

func (m *SocketManager) Serv() {
	// 整理数据并发送至客户端
	// 计时器
	ticker := time.NewTicker(3 * time.Second)

	go func() {
		for {
			select {
			case <-ticker.C:
				go m.collectAndBroadcastData()
			}
		}
	}()

	iris.RegisterOnInterrupt(func() {
		ticker.Stop()
	})
}

func (m *SocketManager) collectAndBroadcastData() {

	defer func() {
		if err := recover(); err != nil {
			log.Errorf("broadcast error: %v", err)
		}
	}()

	conn := core.BK.Redis.OTEPool.Get()
	defer conn.Close()

	for pair := range m.coinPairs {
		connections := m.ws.GetConnectionsByRoom(pair)
		if len(connections) == 0 || connections == nil {
			continue
		}

		log.Infof("coin pair[%s] item length: %d", pair, len(connections))

		floatPrice, ok := m.floatPrice[pair]
		if !ok {
			floatPrice = DEFAULT_FLOAT_PRICE
		}

		floatNumber, ok := m.floatNumber[pair]
		if !ok {
			floatNumber = DEFAULT_FLOAT_NUMBER
		}

		// 处理交易单
		broadcast(connections, pair, constants.MSGID_ORDER_RESPONSE, order(conn, pair, floatNumber, floatPrice))

		// 处理挂单列表
		broadcast(connections, pair, constants.MSGID_TRUST_RESPONSE, trusts(conn, pair, floatPrice))
	}
}

func broadcast(conns []websocket.Connection, pair string, msgId constants.MsgId, data interface{}) {
	for _, conn := range conns {
		if conn == nil {
			continue
		}
		Success(conn, pair, msgId, data)
	}
}

// 初始化交易对
func (m *SocketManager) Init() *SocketManager {
	m.initCoinPair()
	m.initWebscoketServer()
	return m
}

func (m *SocketManager) initWebscoketServer() {
	defer func() {
		if err := recover(); err != nil {
			log.Errorf("init websocket server error, msg: %v", err)
		}
	}()

	m.ws.OnConnection(func(conn websocket.Connection) {

		log.Infof("conn id: %s connected", conn.ID())

		defer func() {
			if err := recover(); err != nil {
				log.Errorf("catch error on connection: %v", err)
			}
		}()

		conn.OnMessage(func(msg []byte) {
			defer func() {
				if err := recover(); err != nil {
					log.Errorf("on message error: %v", err)
					if conn != nil {
						conn.Disconnect()
					}
				}
			}()

			log.Infof("receive msg: %v", string(msg))
			var params websocketParams
			if err := json.Unmarshal(msg, &params); err != nil {
				Error(conn, "", constants.WS_PARAMETER, "invalid parameter")
				log.Errorf("json unmarshal message failed and disconnected the client, msg: %s", err.Error())
				conn.Disconnect()
				return
			}

			if params.MessageId == 0 {
				Error(conn, "", constants.WS_PARAMETER, "")
				conn.Disconnect()
				return
			}

			switch params.MessageId {
			case constants.MSGID_PING_REQUEST:
				if params.Text != "ping" {
					Error(conn, params.Pair, constants.WS_PARAMETER, "")
					conn.Disconnect()
					return
				}
				Success(conn, "", constants.MSGID_PONG_RESPONSE, map[string]interface{}{
					"text": 		"pong",
					"timestamp": 	int(time.Now().UnixNano() / 1e6),
				})
			case constants.MSGID_JOIN_REQUEST:
				if params.Pair == "" || !m.checkAndGetPairExists(params.Pair) {
					Error(conn, params.Pair, constants.WS_NO_COINPAIR, "")
					m.DisConnectSocket(conn)
					return
				}

				// 检查用户是不是在另一个交易对中，如果用户是交易对之间的切换，需要将它从原来的交易对踢出来
				// 如果还是原来的交易对，直接返回
				oldCoinPairName := m.GetClientCoinPair(conn)
				if oldCoinPairName != "" {
					if conn.IsJoined(params.Pair) {
						Error(conn, params.Pair, constants.WS_ERROR_REQUEST, "")
						m.DisConnectSocket(conn)
						return
					} else {
						conn.Leave(oldCoinPairName)
					}
				}

				log.Infof("start join client manager, conn: %v, pair: %v", conn.ID(), params.Pair)
				m.SaveClientCoinPair(conn, params.Pair)
				conn.Join(params.Pair)
				Success(conn, "", constants.MSGID_JOIN_RESPONSE, map[string]string{"text": "success"})
			}
		})

		conn.OnError(func(e string) {
			log.Errorf("socket on error: %v", e)
			m.DisConnectSocket(conn)
		})

		// 断开连接
		conn.OnDisconnect(func() {
			defer func() {
				if err := recover(); err != nil {
					log.Errorf("disconnection recover error: %v", err)
				}
			}()

			m.RemoveClientCoinPair(conn)

			log.Infof("conn id: %s closed", conn.ID())
		})
	})
}

func (m *SocketManager) initCoinPair() {
	coinPairs, err := model.CoinPairModel.GetAll()
	if err != nil {
		log.Errorf("init coin pair error, msg: %s", err)
		return
	}

	m.Lock()
	defer m.Unlock()
	for _, pair := range coinPairs {
		m.coinPairs[pair.Name] = true
		m.floatPrice[pair.Name] = pair.FloatPrice
		m.floatNumber[pair.Name] = pair.FloatNumber
		log.Infof("init websocket coin pair success, name: %s", pair.Name)
	}
}

func (m *SocketManager) DisConnectSocket(connection websocket.Connection) {

	if connection == nil {
		return
	}

	if err := connection.Disconnect(); err != nil {
		log.Errorf("disconnect error: %s", err.Error())
	}
}

// 获取交易对
func (m *SocketManager) GetClientCoinPair(connection websocket.Connection) string {
	return m.clients[connection]
}

func (m *SocketManager) SaveClientCoinPair(connection websocket.Connection, coinPair string) {
	m.Lock()
	m.clients[connection] = coinPair
	m.Unlock()
}

func (m *SocketManager) RemoveClientCoinPair(connection websocket.Connection) {
	m.Lock()
	defer m.Unlock()
	if coinPair, ok := m.clients[connection]; ok {
		connection.Leave(coinPair)
		delete(m.clients, connection)
	}
}

func (m *SocketManager) CheckClientExists(connection websocket.Connection) bool {
	m.Lock()
	defer m.Unlock()
	if _, ok := m.clients[connection]; ok {
		return true
	}
	return false
}

func (m *SocketManager) checkAndGetPairExists(coinPair string) bool {
	exists := m.coinPairs[coinPair]
	if exists {
		return exists
	}

	// rebuild coinPairs
	item, err := model.CoinPairModel.GetFirst(coinPair)
	if err != nil {
		log.Errorf("coin pair not exists, name: %s", coinPair)
		return false
	}

	if item.Enabled != model.COIN_PAIR_ENABLED {
		log.Errorf("coin pair status error, enabled: %s", item.Enabled)
		return false
	}

	m.Lock()
	m.coinPairs[coinPair] = true
	m.Unlock()

	return true
}

// ------------------------------------------------------------
// dispatch

type Dispatch int

func (Dispatch) RegistFunc(msgId int, cb func()) {

}

func (Dispatch) GetFunc() func() {
	return func() {

	}
}


